/**
 * 
 */
package queryevaluator;

import static org.junit.Assert.*;

import inputprocessor.XMLNavigator;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;

import generatedparser.ASTStart;
import generatedparser.ASTXQuery;
import generatedparser.ASTap;
import generatedparser.ASTxquery_identifier;
import generatedparser.ParseException;
import generatedparser.XPath;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * @author RaresMan
 *
 */
public class XQueryEvaluatorTest {

	private Map<String, List<String>> ap_testCases;
	private Map<String, String> xquery_testCases;
	
	/**
	 * @throws java.lang.Exception
	 */
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
	}

	/**
	 * @throws java.lang.Exception
	 */
	@AfterClass
	public static void tearDownAfterClass() throws Exception {
	}

	/**
	 * @throws java.lang.Exception
	 */
	@Before
	public void setUp() throws Exception {
				
		/**
		 * AP Test Cases
		 */
		
		ap_testCases = new HashMap<String, List<String>>();
		
		String testCase 		 		= null;
		List<String> expectedNodeNames 	= null;
		    					
		// Case 1
		testCase 		  = "doc(\"input/test.xml\")/PLAY/PERSONAE/PGROUP/PERSONA;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 2
		testCase 		  = "doc(\"input/test.xml\")/(PLAY/PERSONAE/PGROUP)/PERSONA;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 3
		testCase 		  = "doc(\"input/test.xml\")/(PLAY/PERSONAE)/(PGROUP)/(PERSONA);";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		ap_testCases.put(testCase, expectedNodeNames);
				
		

		// Case 4
		testCase 		  = "doc(\"input/test.xml\")/PLAY/PERSONAE;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PERSONAE");
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 5
		testCase 		  = "doc(\"input/test.xml\")/PLAY/PERSONAE/PGROUP;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PGROUP");
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 6
		testCase 		  = "doc(\"input/test.xml\")/PLAY/PERSONAE/PGROUP/PERSONA;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 7
		testCase 		  = "doc(\"input/test.xml\")//PERSONA;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		ap_testCases.put(testCase, expectedNodeNames);
				
		// Case 8
		testCase 		  = "doc(\"input/test.xml\")//P;";		
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("P");
		expectedNodeNames.add("P");
		expectedNodeNames.add("P");
		expectedNodeNames.add("P");
		expectedNodeNames.add("P");
		expectedNodeNames.add("P");
		ap_testCases.put(testCase, expectedNodeNames);
		
		// Case 9
		testCase 		  = "doc(\"input/test.xml\")//PERSONAE/PGROUP;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PGROUP");
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 10
		testCase 		  = "doc(\"input/test.xml\")//PERSONAE/PGROUP/PERSONA;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");		
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 11
		testCase 		  = "doc(\"input/test.xml\")/PLAY//P;";		
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("P");
		expectedNodeNames.add("P");
		expectedNodeNames.add("P");
		expectedNodeNames.add("P");
		expectedNodeNames.add("P");
		expectedNodeNames.add("P");
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 12
		testCase 		  = "doc(\"input/test.xml\")/PLAY//PERSONA;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 13
		testCase 		  = "doc(\"input/test.xml\")/PLAY//PGROUP;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PGROUP");
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 14
		testCase 		  = "doc(\"input/test.xml\")/PLAY//GRPDESCR;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("GRPDESCR");
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 15
		testCase 		  = "doc(\"input/test.xml\")//PERSONAE//GRPDESCR;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("GRPDESCR");
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 16
		testCase 		  = "doc(\"input/test.xml\")//PERSONAE//PGROUP//PERSONA;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");
		expectedNodeNames.add("PERSONA");		
		ap_testCases.put(testCase, expectedNodeNames);

		// Case 17 TODO: Is this right?
		testCase 		  = "doc(\"input/test.xml\")/PLAY/FM,PERSONAE;";		    	
		expectedNodeNames = new ArrayList<String>();
		expectedNodeNames.add("PERSONAE");
		expectedNodeNames.add("FM");
		expectedNodeNames.add("FM");		
		ap_testCases.put(testCase, expectedNodeNames);

		
//		NOT SURE: 		
//		doc("input/test.xml")//PERSONAE//GRPDESCR,PERSONAE;
//		doc("input/test.xml")//PERSONAE//GRPDESCR,FM,PERSONAE//GRPDESCR,PERSONA;
//
//		doc("input/test.xml")/PLAY//P,PLAY/PERSONAE;
//		doc("input/test.xml")/PLAY/FM,PLAY//P;
		
		/**
		 * XQuery Test Cases
		 */
		
		xquery_testCases = new HashMap<String, String>();
		
		String input = 
			"for $line in doc(\"input/test.xml\")/PLAY/ACT/SCENE/SPEECH/LINE " + 
			"where $line/text() = \"Et tu, Brute! Then fall, Caesar.\" " + 
			"return $line;";
		String output = 
			"<?xml version=\"1.0\" encoding=\"ISO-8859-1\" standalone=\"no\"?>\n" +
			"<LINE>Et tu, Brute! Then fall, Caesar.</LINE>\n";
		
		xquery_testCases.put(input, output);
		
		input = 
	        "<result>{\n" + 
	            "for $a in doc(\"input/j_caesar.xml\")/PLAY/ACT;\n" +
	                "$sc in $a/SCENE;\n" +
	                "$sp in $sc/SPEECH\n" +	                
	                "where $sp/LINE/text() = \"Et tu, Brute! Then fall, Caesar.\"\n" +
	            "return <who>{ $sp/SPEAKER/text() }</who>,\n" +
	                "<when>{\n" +
	                    "<act>{$a/TITLE/text()}</act>,\n" +
	                    "<scene>{$sc/TITLE/text()}</scene>\n" +
	                   "}</when>\n" +
	        "}</result>;\n";
		
		output = 
			"<?xml version=\"1.0\" encoding=\"ISO-8859-1\" standalone=\"no\"?>\n" +
			"<result>\n" +
		    "    <who>CAESAR</who>\n" +
		    "    <when>\n" +
		    "        <act>ACT III</act>\n" +
		    "        <scene>SCENE I. Rome. Before the Capitol; the Senate sitting above.</scene>\n" +
		    "    </when>\n" +
			"</result>\n";
		
		xquery_testCases.put(input, output);
	}

	/**
	 * @throws java.lang.Exception
	 */
	@After
	public void tearDown() throws Exception {
	}

	/**
	 * Test method for {@link queryevaluator.XQueryEvaluator#evaluate_ap(generatedparser.ASTap)}.
	 */
	@Test
	public void testEvaluate_ap() {
		
		System.out.println(
				"*******************************************************************************\n" + 
				"*******************  AP/XPATH EVALUATION TESTS ********************************\n" +
				"*******************************************************************************\n");
						
    	for(String testCase : ap_testCases.keySet()) {
        	
    		List<Node> resultList = null;
        	
        	try {
        		System.out.println("Evaluating: " + testCase);
        		resultList = get_ap_EvaluationResults(testCase);
        		System.out.println();
        	} catch (Exception e) {
        		fail(e.getMessage());
        	}

        	// Make sure it's not null
    		assertNotNull(resultList);

    		// Actuals
    		Object[] actualNodeNames 	= extractNodeNames(resultList).toArray();		    		
    		Object[] expectedNodeNames 	= ap_testCases.get(testCase).toArray();
    		    		
    		// Run test
    		assertArrayEquals(testCase, expectedNodeNames, actualNodeNames);			    	
    	}    	
	}

	@Test
	public void testEvaluate_xquery() {
				
		System.out.println(
			"*******************************************************************************\n" + 
			"*******************  XQUERY EVALUATION TESTS **********************************\n" +
			"*******************************************************************************\n");
		
    	for(String testCase : xquery_testCases.keySet()) {
        	
    		String resultXML = null;
        	
        	try {
        		System.out.println("Evaluating:\n" + testCase);
        		resultXML = get_xquery_EvaluationResults(testCase);
        		System.out.println();
        	} catch (Exception e) {
        		fail(e.getMessage());
        	}

        	// Make sure it's not null
    		assertNotNull(resultXML);
    		    		
    		String expectedXML = xquery_testCases.get(testCase);
    		
    		// Run test
    		assertEquals(expectedXML, resultXML);
    	}    	
	}

	/**
	 * Given a string representing a testcase, return the XML nodes for evaluating
	 * the particular testcase
	 * @param testCase
	 * @return
	 * @throws Exception
	 */
	private List<Node> get_ap_EvaluationResults(String testCase) throws Exception {

		ByteArrayInputStream testStream = new ByteArrayInputStream(testCase.getBytes());
    	XPath t 						= new XPath(testStream);

		ASTStart startNode;
		startNode = t.Start();
		XQueryEvaluator ev = new XQueryEvaluator();

		ASTXQuery xqNode 				= (ASTXQuery) startNode.jjtGetChild(0);
		ASTxquery_identifier xqIdNode 	= (ASTxquery_identifier) xqNode.jjtGetChild(0);
		ASTap apNode 					= (ASTap) xqIdNode.jjtGetChild(0);

		// Save the results for evaluation
		return ev.evaluate_ap(apNode);	
	}
	
	private String get_xquery_EvaluationResults(String testCase) throws Exception {
		ByteArrayInputStream testStream = new ByteArrayInputStream(testCase.getBytes());
    	XPath t 						= new XPath(testStream);

		ASTStart startNode;
		startNode = t.Start();
		XQueryEvaluator ev = new XQueryEvaluator();
		return ev.evaluate(startNode);
	}
	
	/**
	 * Extract node names for the given list of xml nodes
	 * @param resultList
	 * @return
	 */
	private List<String> extractNodeNames(List<Node> resultList) {
		List<String> nameList = new ArrayList<String>();
		
		for(Node resultNode : resultList) {
			if ( resultNode instanceof Node)
				nameList.add(((Node) resultNode).getNodeName());
			else
				nameList.add(resultNode.toString());		
		}
		
		return nameList;
	}
	
	@Test
	public void test_evaluate_naive_join() throws TransformerConfigurationException, ParserConfigurationException, TransformerException {
		
		System.out.println(
				"*******************************************************************************\n" + 
				"*******************  NAIVE JOIN EVALUATION TESTS ********************************\n" +
				"*******************************************************************************\n");
		XQueryEvaluator ev = new XQueryEvaluator();
		Document doc = XMLNavigator.getNewDocument();
		
		// Setup 
		List<Node> tupleList1 = new ArrayList<Node>();
		List<Node> tupleList2 = new ArrayList<Node>();
		
		List<String> tag1List = new ArrayList<String>();
		List<String> tag2List = new ArrayList<String>();
				

		List<String> tagNames = new ArrayList<String>();
		tagNames.add("a");
		tagNames.add("b");
		tagNames.add("c");		
		List<String> tagValues = new ArrayList<String>();
		tagValues.add("1");
		tagValues.add("2");
		tagValues.add("3");		
		tupleList1.add(createTuple(doc, tagNames, tagValues));
		
		tagValues = new ArrayList<String>();
		tagValues.add("4");
		tagValues.add("5");
		tagValues.add("6");
		tupleList1.add(createTuple(doc, tagNames, tagValues));
				
		
		System.out.println(ev.createXMLDocFromNodeList(tupleList1));

		tagNames = new ArrayList<String>();
		tagNames.add("d");
		tagNames.add("e");
		tagNames.add("f");		
		tagValues = new ArrayList<String>();
		tagValues.add("1");
		tagValues.add("2");
		tagValues.add("3");		
		tupleList2.add(createTuple(doc, tagNames, tagValues));
		
		tagValues = new ArrayList<String>();
		tagValues.add("7");
		tagValues.add("8");
		tagValues.add("9");
		tupleList2.add(createTuple(doc, tagNames, tagValues));

		System.out.println(ev.createXMLDocFromNodeList(tupleList2));
			
		tag1List.add("b");
		tag1List.add("c");
		
		tag2List.add("e");
		tag2List.add("f");

		
		List<Node> result = ev.evaluate_naive_join(tupleList1, tupleList2, tag1List, tag2List);
		
		String xmlDocString = ev.createXMLDocFromNodeList(result);
		System.out.println(xmlDocString);
		
	}

	public Node createTuple(Document doc, List<String> tagNames, List<String> tagValues) {
		
		Node tupleNode = doc.createElement("tuple");
		
		for(int i = 0; i<tagNames.size();i++) {
			String tagName 		= tagNames.get(i);
			String tagValue 	= tagValues.get(i); 
			Node tupleChild = doc.createElement(tagName);
			tupleChild.setTextContent(tagValue);		
			tupleNode.appendChild(tupleChild);

		}
		
		return tupleNode;
	}
	
}
